/**
 * 手写js各种实现
 */
/**浅克隆
 * 只能拷贝一层对象，有对象的嵌套，则浅拷贝无能为力
*/

let arr = [1, 2, 3];
let newArr = arr.slice();
newArr[0] = 100;

console.log(arr);//[1, 2, 3]
//or
let newArr = [...arr];
//concat
let newArr = arr.concat();
//objec.assign
let obj = { name: 'sy', age: 18 };
const obj2 = Object.assign({}, obj, {name: 'sss'});
console.log(obj2);//{ name: 'sss', age: 18 }


/**深克隆 */
//最简单版本
JSON.parse(JSON.stringify());
//只考虑对象
function deepClone(obj) {
    if (obj === null) return null;
    if (typeof obj !== "object") return obj;
    if (obj instanceof RegExp) {
        return new RegExp(obj);
    }
    if (obj instanceof Date) {
        return new Date(obj);
    }
    let newObj = new obj.constructor();
    for (let key in newObj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = deepClone(obj[key]);
        }
    }
    return newObj;
}
//考虑数组和对象
var deepCopy = function (obj) {
    if (typeof obj !== "object") return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] =
                typeof obj[key] === "object" ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
};

/**防抖和节流 */
//防抖：动作绑定事件，动作发生后一段时间后触发事件，在此过程中，如果该动作又发生，则重新等待一定时间后触发：
function debounce(func, time) {
    let timer = null;
    return () => {
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
            func.apply(this, arguments);
        }, time);
    };
}
//复杂版防抖：添加immediate是否立即执行的逻辑， 增加返回值，能取消debounce
function debounce(func, wait, immediate) {
    let timer, result;
    const debounced = () => {
        timer && clearTimeout(timer);
        if (immediate) {
            let callNow = !timer;
            timer = setTimeout(() => {
                timer = null;
            }, wait);
            if (callNow) result = func.apply(this, arguments);
        } else {
            timer = setTimeout(() => {
                func.apply(this, arguments);
            }, wait);
        }
        return result;
    };
    debounced.cancel = function () {
        clearTimeout(timer);
        timer = null;
    };
}
//节流：动作绑定事件，动作发生后一段时间后触发事件，在此过程中，如果该动作又发生，则无视该动作，直到事件执行完后，才能重新触发；
function throttle(func, time) {
    let activeTime = 0;
    return () => {
        let current = Date.now();
        if (current - activeTime > time) {
            func.apply(this, arguments);
            activeTime = Date.now();
        }
    };
}
//复杂版节流：鼠标移入能立即执行，停止触发的时候还能再执行一次
function throttle(func, wait) {
    let previous = 0;
    let timer;
    let context, args;
    const later = () => {
        previous = Date.now();
        timer = null;
        func.apply(context, args);
    };

    const throttled = function () {
        let context = this;
        let args = arguments;
        let current = Date.now();
        let remaining = wait - (current - previous);
        if (remaining <= 0 || remaining > wait) {
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timer) {
            timer = setTimeout(later, remaining);
        }
    };
    return throttled;
}

/**
 * 实现一个new
 * 用new Object() 的方式新建了一个对象 obj
 * 取出第一个参数，就是我们要传入的构造函数。此外因为 shift 会修改原数组，所以 arguments 会被去除第一个参数
 * 将 obj 的原型指向构造函数，这样 obj 就可以访问到构造函数原型中的属性
 * 使用 apply，改变构造函数 this 的指向到新建的对象，这样 obj 就可以访问到构造函数中的属性
 * 返回 obj
 */
function _new() {
    let obj = new Object();
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    let res = Constructor.apply(obj, arguments);
    return typeof res === "object" ? res : obj; //和构造函数里是否有返回值有关
}
function People(name,age) {
    this.name = name
    this.age = age
}

let peo = _new(People,'Bob',22)
console.log(peo.name)
console.log(peo.age)
/**实现一个flatten
 *
 */
//1.迭代写法：
let arr = [1, 2, [3, 4, 5, [6, 7], 8], 9, 10, [11, [12, 13]]];

function flatten(arr) {
    let arrs = [...arr];
    let newArr = [];
    while (arrs.length) {
        let cur = arrs.shift();
        if (Array.isArray(cur)) {
            arrs.unshift(...cur);
        } else {
            newArr.push(cur);
        }
    }
    return newArr;
}
console.log(flatten(arr));
//2.递归写法
function flatten(arr) {
    let arrs = [];
    arr.map((item) => {
        if (Array.isArray(item)) {
            arrs.push(...flatten(item));
        } else {
            arrs.push(item);
        }
    });
    return arrs;
}
//3.字符串转换
function flatten(arr) {
    return arr
        .toString()
        .split(",")
        .map((s) => parseFloat(s));
}

//4.ES6 flat
arr.flat(Infinity);

//5.replace + split
let str = JSON.stringify(arr);
ary = str.replace(/(\[|\])/g, '').split(',')

/**
 * proxy实现简单的数据绑定: 参见vue工程proxy.html
 */

/**
 * 实现一个add函数 满足以下功能：
 * add(1); 			// 1
 * add(1)(2);       // 3
 * add(1)(2)(3)；   // 6
 * add(1)(2, 3);   // 6
 * add(1, 2)(3);   // 6
 * add(1, 2, 3);   // 6
 */
function add() {
    let args = [...arguments];
    let addfun = function () {
        args.push(...arguments);
        return addfun;
    };
    addfun.toString = function () {
        return args.reduce((a, b) => {
            return a + b;
        }, 0);
    };
    return addfun;
}

/**
 * 手写localstorage
 */
const valueMap = {};
class LocalStorage {
    getItem(key) {
        const sKey = String(key);
        if (valueMap.has(key)) {
            return String(valueMap.get(sKey));
        }
        return null;
    }
    setItem(key, val) {
        valueMap.set(string(key), String(val));
    }
    removeItem(key) {
        valueMap.delete(String(key));
    }
    clear() {
        valueMap.clear();
    }
    key(i) {
        if (arguments.length === 0) {
            throw new TypeError(
                "Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."
            ); // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key.
        }
        var arr = Array.from(valueMap.keys());
        return arr[i];
    }
    get length() {
        return valueMap.size;
    }
}
const instance = new LocalStorage();
global.localStorage = new Proxy(instance, {
    set(target, key, value) {
        if (LocalStorage.prototype.hasOwnProperty(key)) {
            instance[key] = value;
        } else {
            instance.setItem(key, value);
        }
        return true;
    },
    get(target, key) {
        if (LocalStorage.prototype.hasOwnProperty(key)) {
            return instance[key];
        }
        if (valueMap.has(key)) {
            return instance.getItem(key);
        }
    },
});

/**
 * 千分位：将 '10000000000' 形式的字符串，以每 3 位进行分隔展示 '10.000.000.000'
 */
"100000000000".replace(/(\d)(?=(\d{3})+\b)/g, "$1.");

str.split("")
    .reverse()
    .reduce((prev, cur, index) =>
        (index + 1) % 3 == 0 ? "." + cur + prev : cur + prev
    );
/**
 * 手写async/await的实现
 * 本质上是实现一个co
 */

function* read() {
    try {
        let name = yield fs.readFile("name.txt", "utf8");
        let age = yield fs.readFile(name, "utf8");
        return age;
    } catch (e) {
        console.log(e);
    }
}

//原始写法：
//  let it = read();
//  let {value,done} = it.next();
//  value.then(data=>{
//      let {value,done} = it.next(data);
//      value.then(data=>{
//          console.log(data)
//      })
//  })
const co = (it) => {
    return new Promise((resolve, reject) => {
        //异步迭代靠的是回调函数
        function next(data) {
            let { value, done } = it.next(data);
            if (!done) {
                Promise.resolve(value).then(next, reject);
            } else {
                resolve(value);
            }
        }
        next();
    });
};
co(read()).then((data) => {
    console.log(data);
});

/**
 * 实现一个bind
 * bind做了哪些事情：
 * 1.对于普通函数，绑定this指向
 * 2.对于构造函数，要保证原函数的原型对象上的属性不能丢失(一个绑定函数也能使用new操作符创建对象,bind 时指定的 this 值会失效)
 */
 Function.prototype.bind2 = function(context){
    if(typeof this !== 'function'){
        throw new Error('function.prototype.bind--what is trying to be bound is not callable')
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments,1);

    var fBound = function(){
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fBound ? this : context,args.concat(bindArgs));
    }
    //修改返回函数的 prototype 为绑定函数的 prototype，实例就可以继承绑定函数的原型中的值
    fBound.prototype = Object.create(this.prototype);
    return fBound;
}

var foo = {
    value: 1,
};

function bar(name, age, num, address) {
    console.log(name, age, num, address);
    console.log(this.value);
}

// 返回了一个函数
var bindFoo = bar.bind2(foo, "cc", 22);
bindFoo(111, "hz"); // 1


/**
 * 实现call/apply 参见call.js文件
 */

/**
 * deepclone深拷贝复杂版
 */
const symbolName = Symbol();
const obj = {
    objNumber: new Number(1),
    number: 1,
    objString: new String("ss"),
    string: "stirng",
    objRegexp: new RegExp("\\w"),
    regexp: /w+/g,
    date: new Date(),
    function: function () {},
    array: [{ a: 1 }, 2],
    [symbolName]: 111,
};
obj.d = obj;

const isObject = (obj) =>
    obj !== null && (typeof obj === "object" || typeof obj === "function");
const isFunction = (obj) => typeof obj === "function";
function deepClone(obj, hash = new WeakMap()) {
    if (hash.get(obj)) {
        // 环处理
        return hash.get(obj);
    }
    if (!isObject(obj)) {
        return obj;
    }

    if (isFunction(obj)) {
        // function返回原引用
        return obj;
    }

    let cloneObj;

    const Constructor = obj.constructor;

    switch (Constructor) {
        case Boolean:
        case Date:
            return new Date(+obj);
        case Number:
        case String:
        case RegExp:
            return new Constructor(obj);
        default:
            cloneObj = new Constructor();
            hash.set(obj, cloneObj);
    }

    [
        ...Object.getOwnPropertyNames(obj),
        ...Object.getOwnPropertySymbols(obj),
    ].forEach((k) => {
        cloneObj[k] = deepClone(obj[k], hash);
    });
    return cloneObj;
}

const o = deepClone(obj);
console.log(o.objNumber === obj.objNumber);
console.log(o.number === obj.number);
console.log(o.objString === obj.objString);
console.log(o.string === obj.string);
console.log(o.objRegexp === obj.objRegexp);
console.log(o.regexp === obj.regexp);
console.log(o.date === obj.date);
console.log(o.function === obj.function);
console.log(o.array[0] === obj.array[0]);
console.log(o[symbolName] === obj[symbolName]);

/**
 * js浮点树精度计算问题：https://github.com/camsong/blog/issues/9
 * https://juejin.cn/post/6856953699051896845
 */
function strip(num, precision = 12) {
    return +parseFloat(num.toPrecision(precision));
}
//数据运算类
function add(num1, num2) {
    const num1Digits = (num1.toString().split(".")[1] || "").length;
    const num2Digits = (num2.toString().split(".")[1] || "").length;
    const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
    return (num1 * baseNum + num2 * baseNum) / baseNum;
}

//实现map
const arr = [1, 3, 4, 5];
Array.prototype.map = function (callbackFn, thisArg) {
    if (this === null || this === undefined) {
        throw new TypeError("Cannot read property 'map' of null or undefined");
    }
    if (Object.prototype.toString.call(callbackFn) !== "[object Function]") {
        throw new TypeError(callbackfn + " is not a function");
    }
    // 草案中提到要先转换为对象
    let O = Object(this);
    let T = thisArg;

    let len = O.length >>> 0;
    let A = new Array(len);
    for (let k = 0; k < len; k++) {
        // 还记得原型链那一节提到的 in 吗？in 表示在原型链查找
        // 如果用 hasOwnProperty 是有问题的，它只能找私有属性
        if (k in O) {
            let kValue = O[k];
            // 依次传入this, 当前项，当前索引，整个数组
            let mappedValue = callbackFn.call(T, kValue, k, O);
            A[k] = mappedValue;
        }
    }
    return A;
};
